/**
 * @file generator.c
 * @brief The foundation for the workload generator.
 */
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <semaphore.h>
#include "cJSON.h"
#include "generator.h"
#include "random.h"
#include "resource_model.h"

/** A counter that keeps track of how many resource threads are ready to begin generating workloads. */
int threads_ready = 0;

/** A mutex for threads_ready */
sem_t threads_ready_mutex;

/** Semaphore which prevents ready resource threads until all threads are ready. */
sem_t threads_start_mutex;

/** Sets up the semaphores and counter that enfore a Rendezvous for all resource threads before any workload can begin. */
void setup_rendezvous();

void setup_generator() {
  printf("Setting up the Workload Generator.\n");
  setup_random_environment();
  setup_rendezvous();
}

void setup_rendezvous() {
  sem_init(&threads_ready_mutex, 0, 1);
  sem_init(&threads_start_mutex, 0 ,0);
}

cJSON * parse_config() {
  FILE *f= fopen("../conf/config.json","rb");
  fseek(f,0,SEEK_END);
  long len=ftell(f);
  fseek(f,0,SEEK_SET);
  char *data=malloc(len+1);
  fread(data,1,len,f);
  fclose(f);
  cJSON *config = cJSON_Parse(data);
  if (!config || config->type != 5) { /* Check for valid JSON and an array.  */
    perror("Config file is either not valid JSON or not an array.\n"); exit(2);
  }
  free(data);
  return config;
}

int workload_generator(cJSON *config, char *resource, resource_model *(*init)(cJSON *), pthread_t *thread)
{
  cJSON *json_resource = NULL; /* Pointer to reference the target resource. */
  cJSON *c = config->child;
  for(c; c; c = c->next) {
    cJSON *c_name = cJSON_GetObjectItem(c, "resource");
    if(c_name && (c_name->type == 4)
       && (strcmp(resource, c_name->valuestring) == 0) )
      {
        json_resource = c;
      }
  }
  if(!json_resource)
    {
      printf("No signature found for %s resource.\n", resource);
      return -1;
    }
  void *model = (void *) init(json_resource);
  if(!model)
    {
      char **buf;
      asprintf(buf, "%s failed to initialize.\n", resource);
      perror(*buf); 
      return -1;
    }
  pthread_create(thread, NULL, thr_resource, model);
}

void * thr_resource(void *config)
{
  setup_schedule();
  struct timespec current_time = {0};
  resource_model *model = (resource_model *) config;

  sem_wait(&threads_ready_mutex);  /* Enforce a Rendezvous before threads begin */
  if(++threads_ready == CNT_RESOURCES) {
    printf("Workload Generator Starting..\n");
    int i = 0;
    for(i; i < CNT_RESOURCES; i++) {
      sem_post(&threads_start_mutex);
    }
  }
  sem_post(&threads_ready_mutex);
  sem_wait(&threads_start_mutex);
  
  /* Start generating a workload for this resource */
  clock_gettime(CLOCK_MONOTONIC, &current_time);
  model->birth(model);
  
  while(has_event()) {
    event *e = next_event();
    struct timespec *time_to_sleep = timeval_diff(&current_time, e->timestamp);
    nanosleep(time_to_sleep, NULL);
    free(time_to_sleep);
    clock_gettime(CLOCK_MONOTONIC,&current_time);
    e->job(e->args);
    free(e->timestamp);
    free(e);
  }
}

struct timespec * ms_from_now(double ms){
  struct timespec *t = malloc(sizeof(struct timespec));
  clock_gettime(CLOCK_MONOTONIC,t);
  unsigned long seconds = ms / 1000;
  unsigned long nanoseconds = (ms - (seconds*1000)) * 1000000;
  unsigned long total_nanoseconds = (t->tv_nsec + nanoseconds) % 1000000000;
  unsigned long seconds_to_add = (t->tv_nsec + nanoseconds) / 1000000000;
  t->tv_sec += seconds + seconds_to_add;
  t->tv_nsec = total_nanoseconds;
  return t;
}

struct timespec *timeval_diff(struct timespec *now, struct timespec *future)
{
  struct timespec *to_sleep = malloc(sizeof(struct timespec));
  if(future->tv_sec < now->tv_sec) {
    to_sleep->tv_sec = to_sleep->tv_nsec = 0;
    return to_sleep;
  }
  else if(future->tv_sec == now->tv_sec && future->tv_nsec < now->tv_nsec) {
    to_sleep->tv_sec = to_sleep->tv_nsec = 0;
    return to_sleep;
  }
  long sec = 0;
  unsigned long nsec = 0;
  sec=(future->tv_sec - now->tv_sec);
  nsec=(future->tv_nsec - now->tv_nsec);
  if(nsec < 0) {
    sec =(long) ((double) sec - ( (double) nsec / 1000000000.0));
    nsec = 1000000000 - nsec;
  }
  to_sleep->tv_sec = sec;
  to_sleep->tv_nsec = nsec;
  return to_sleep;
}
